const http = require('http');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { URL } = require('url');

const ROOT_DIR = __dirname;
const CONTENT_DIR = path.join(ROOT_DIR, 'content');
const PAGES_DIR = path.join(CONTENT_DIR, 'pages');
const UPLOAD_DIR = path.join(ROOT_DIR, 'uploads');
const PUBLIC_DIR = ROOT_DIR;
const PAGE_SCHEMA = require('./schemas/page.schema.json');
const SITE_SCHEMA = require('./schemas/site.schema.json');
const CUBE_SCHEMA = require('./schemas/cube.schema.json');

const SITE_FILE = path.join(CONTENT_DIR, 'site.json');
const DEFAULT_THEME = {
  palette: {
    primary: '#165dff',
    accent: '#ff7d00',
    neutral: '#e6e9ef'
  },
  surfaces: {
    page: '#0b0c10',
    nav: '#101f40',
    cube: '#1e40af',
    gradients: {
      page: { enabled: false, from: '#165dff', to: '#101f40', angle: 135 },
      nav: { enabled: false, from: '#101f40', to: '#165dff', angle: 135 },
      cube: { enabled: false, from: '#1e40af', to: '#0b0c10', angle: 150 }
    }
  },
  text: {
    heading: '#e6e9ef',
    body: '#c6ccda',
    inverse: '#0b0c10',
    interactive: '#165dff'
  }
};
const MIME_TYPES = {
  '.html': 'text/html; charset=utf-8',
  '.css': 'text/css; charset=utf-8',
  '.js': 'application/javascript; charset=utf-8',
  '.json': 'application/json; charset=utf-8',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.gif': 'image/gif',
  '.svg': 'image/svg+xml',
  '.ico': 'image/x-icon',
  '.webp': 'image/webp',
  '.mp4': 'video/mp4'
};

const CACHE_TTL = 60 * 1000;
const PREVIEW_TTL = 5 * 60 * 1000;
const contentCache = new Map();
const previewCache = new Map();

ensureDirectory(CONTENT_DIR);
ensureDirectory(PAGES_DIR);
ensureDirectory(UPLOAD_DIR);

function ensureDirectory(dir) {
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
}

function readJson(file, fallback = null) {
  try {
    return JSON.parse(fs.readFileSync(file, 'utf8'));
  } catch (err) {
    return fallback;
  }
}

function writeJson(file, data) {
  fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf8');
}

function nowIso() {
  return new Date().toISOString();
}

function sanitizeString(value, fallback = '') {
  if (value === null || value === undefined) return fallback;
  if (typeof value === 'string') return value.trim();
  if (typeof value === 'number' || typeof value === 'boolean') return String(value).trim();
  return fallback;
}

function sanitizeRoute(route) {
  const value = sanitizeString(route);
  if (!value) return '';
  if (value.startsWith('http://') || value.startsWith('https://')) return value;
  if (!value.startsWith('/')) return `/${value}`;
  return value;
}

function sanitizeColor(value, fallback = '#2B63FF') {
  const color = sanitizeString(value, '').toLowerCase();
  if (/^#[0-9a-f]{6}$/.test(color) || /^#[0-9a-f]{3}$/.test(color)) {
    return color;
  }
  return fallback;
}

function clampAngle(value, fallback = 135) {
  const num = Number(value);
  if (!Number.isFinite(num)) return fallback;
  const normalized = Math.round(num) % 360;
  return normalized >= 0 ? normalized : normalized + 360;
}

function sanitizeGradientConfig(value, fallback = {}) {
  const defaults = {
    enabled: Boolean(fallback.enabled),
    from: sanitizeColor(fallback.from, '#165dff'),
    to: sanitizeColor(fallback.to, '#101f40'),
    angle: clampAngle(fallback.angle, 135)
  };
  if (!value || typeof value !== 'object') {
    return defaults;
  }
  let enabled = Boolean(value.enabled);
  if (typeof value.enabled === 'string') {
    const lowered = value.enabled.trim().toLowerCase();
    if (['false', '0', 'no', 'off'].includes(lowered)) {
      enabled = false;
    } else if (['true', '1', 'yes', 'on'].includes(lowered)) {
      enabled = true;
    }
  } else if (typeof value.enabled === 'boolean') {
    enabled = value.enabled;
  }
  return {
    enabled,
    from: sanitizeColor(value.from, defaults.from),
    to: sanitizeColor(value.to, defaults.to),
    angle: clampAngle(value.angle, defaults.angle)
  };
}

function toArray(input) {
  return Array.isArray(input) ? input : [];
}

function cloneDeep(obj) {
  return JSON.parse(JSON.stringify(obj));
}

function isPlainObject(value) {
  return !!value && typeof value === 'object' && !Array.isArray(value);
}

function resolveSchemaRef(schema, ref) {
  if (!ref || typeof ref !== 'string' || !ref.startsWith('#/')) return null;
  const parts = ref
    .slice(2)
    .split('/')
    .map((segment) => segment.replace(/~1/g, '/').replace(/~0/g, '~'));
  let current = schema;
  for (const part of parts) {
    if (!current || typeof current !== 'object') return null;
    current = current[part];
  }
  return current || null;
}

function applySchemaDefaults(schema, value, root = schema) {
  if (!schema) return value;
  if (schema.$ref) {
    const resolved = resolveSchemaRef(root, schema.$ref);
    return applySchemaDefaults(resolved, value, root);
  }
  if (schema.anyOf) {
    if (value === undefined && Object.prototype.hasOwnProperty.call(schema, 'default')) {
      return cloneDeep(schema.default);
    }
    if (value === null) {
      const nullVariant = schema.anyOf.find((option) => option && option.type === 'null');
      if (nullVariant) {
        return null;
      }
    }
    const objectVariant = schema.anyOf.find((option) => option && option.type && option.type !== 'null');
    if (objectVariant) {
      return applySchemaDefaults(objectVariant, value, root);
    }
    return value;
  }

  const type = schema.type;
  let current = value;
  if (current === undefined || current === null) {
    if (Object.prototype.hasOwnProperty.call(schema, 'default')) {
      current = cloneDeep(schema.default);
    } else if (type === 'object') {
      current = {};
    } else if (type === 'array') {
      current = [];
    }
  }

  if (type === 'object') {
    const result = isPlainObject(current) ? { ...current } : {};
    const props = schema.properties || {};
    Object.keys(props).forEach((key) => {
      result[key] = applySchemaDefaults(props[key], result[key], root);
      if (result[key] === undefined) {
        delete result[key];
      }
    });
    if (schema.additionalProperties === false) {
      Object.keys(result).forEach((key) => {
        if (!props[key]) {
          delete result[key];
        }
      });
    }
    return result;
  }

  if (type === 'array') {
    const itemsSchema = schema.items;
    if (!Array.isArray(current)) {
      current = [];
    }
    if (itemsSchema) {
      return current.map((entry) => applySchemaDefaults(itemsSchema, entry, root));
    }
    return Array.isArray(current) ? current : [];
  }

  return current;
}

function validateSchemaValue(schema, value, path, errors, root = schema) {
  if (!schema) return errors;
  if (schema.$ref) {
    const resolved = resolveSchemaRef(root, schema.$ref);
    return validateSchemaValue(resolved, value, path, errors, root);
  }

  if (schema.anyOf) {
    const outcomes = schema.anyOf.map((option) => {
      const optionErrors = [];
      validateSchemaValue(option, value, path, optionErrors, root);
      return optionErrors;
    });
    if (outcomes.some((entry) => entry.length === 0)) {
      return errors;
    }
    const firstIssue = outcomes.find((entry) => entry.length)?.[0];
    errors.push(firstIssue || `${path} 不符合预期结构`);
    return errors;
  }

  const type = schema.type;
  if (type === 'object') {
    if (!isPlainObject(value)) {
      errors.push(`${path} 需为对象`);
      return errors;
    }
    const props = schema.properties || {};
    if (schema.additionalProperties === false) {
      Object.keys(value).forEach((key) => {
        if (!props[key]) {
          errors.push(`${path}.${key} 非允许字段`);
        }
      });
    }
    if (Array.isArray(schema.required)) {
      schema.required.forEach((key) => {
        if (value[key] === undefined || value[key] === null) {
          errors.push(`${path}.${key} 为必填字段`);
        }
      });
    }
    Object.keys(props).forEach((key) => {
      if (value[key] !== undefined) {
        validateSchemaValue(props[key], value[key], `${path}.${key}`, errors, root);
      } else if (Array.isArray(schema.required) && schema.required.includes(key)) {
        errors.push(`${path}.${key} 为必填字段`);
      }
    });
    return errors;
  }

  if (type === 'array') {
    if (!Array.isArray(value)) {
      errors.push(`${path} 需为数组`);
      return errors;
    }
    if (typeof schema.minItems === 'number' && value.length < schema.minItems) {
      errors.push(`${path} 至少包含 ${schema.minItems} 个条目`);
    }
    if (schema.items) {
      value.forEach((entry, index) => {
        validateSchemaValue(schema.items, entry, `${path}[${index}]`, errors, root);
      });
    }
    return errors;
  }

  if (type === 'string') {
    if (typeof value !== 'string') {
      errors.push(`${path} 需为字符串`);
      return errors;
    }
    if (typeof schema.minLength === 'number' && value.trim().length < schema.minLength) {
      errors.push(`${path} 至少包含 ${schema.minLength} 个字符`);
    }
    if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
      errors.push(`${path} 取值须为 ${schema.enum.join(', ')}`);
    }
    return errors;
  }

  if (type === 'boolean') {
    if (typeof value !== 'boolean') {
      errors.push(`${path} 需为布尔值`);
    }
    return errors;
  }

  if (type === 'number' || type === 'integer') {
    if (typeof value !== 'number' || Number.isNaN(value)) {
      errors.push(`${path} 需为数值`);
    }
    return errors;
  }

  if (type === 'null') {
    if (value !== null) {
      errors.push(`${path} 需为 null`);
    }
    return errors;
  }

  return errors;
}

function enforcePageSchema(raw) {
  const hydrated = applySchemaDefaults(PAGE_SCHEMA, raw, PAGE_SCHEMA);
  const errors = [];
  validateSchemaValue(PAGE_SCHEMA, hydrated, '$', errors, PAGE_SCHEMA);
  return { value: hydrated, errors };
}

function enforceSiteSchema(raw) {
  const hydrated = applySchemaDefaults(SITE_SCHEMA, raw, SITE_SCHEMA);
  const errors = [];
  validateSchemaValue(SITE_SCHEMA, hydrated, '$', errors, SITE_SCHEMA);
  return { value: hydrated, errors };
}

function enforceCubeSchema(raw) {
  const hydrated = applySchemaDefaults(CUBE_SCHEMA, raw, CUBE_SCHEMA);
  const errors = [];
  validateSchemaValue(CUBE_SCHEMA, hydrated, '$', errors, CUBE_SCHEMA);
  return { value: hydrated, errors };
}

function normalizeAction(action) {
  if (!action || typeof action !== 'object') return null;
  const label = sanitizeString(action.label);
  const href = sanitizeString(action.href);
  if (!label && !href) return null;
  const target = action.target === '_blank' ? '_blank' : '_self';
  return {
    label,
    href,
    target
  };
}

function normalizeMedia(media) {
  if (!media || typeof media !== 'object') return null;
  const src = sanitizeString(media.src);
  const kind = ['image', 'video'].includes(media.type) ? media.type : 'image';
  if (!src) return null;
  return {
    type: kind,
    src,
    alt: sanitizeString(media.alt),
    caption: sanitizeString(media.caption),
    thumbnail: sanitizeString(media.thumbnail),
    ratio: sanitizeString(media.ratio)
  };
}

function normalizeHero(hero = {}) {
  return {
    eyebrow: sanitizeString(hero.eyebrow),
    title: sanitizeString(hero.title),
    subtitle: sanitizeString(hero.subtitle),
    lead: sanitizeString(hero.lead),
    description: sanitizeString(hero.description),
    summary: sanitizeString(hero.summary),
    badge: sanitizeString(hero.badge),
    meta: toArray(hero.meta).map((item) => sanitizeString(item)).filter(Boolean),
    actions: toArray(hero.actions).map(normalizeAction).filter(Boolean),
    cta: normalizeAction(hero.cta),
    media: normalizeMedia(hero.media),
    metrics: toArray(hero.metrics)
      .map((metric) => ({
        label: sanitizeString(metric?.label),
        value: sanitizeString(metric?.value),
        description: sanitizeString(metric?.description)
      }))
      .filter((metric) => metric.label || metric.value)
  };
}

function normalizeCard(card = {}) {
  const normalized = {
    title: sanitizeString(card.title),
    subtitle: sanitizeString(card.subtitle),
    description: sanitizeString(card.description),
    badge: sanitizeString(card.badge),
    media: normalizeMedia(card.media),
    actions: toArray(card.actions).map(normalizeAction).filter(Boolean)
  };
  return normalized;
}

function normalizeSection(section = {}) {
  const type = sanitizeString(section.type);
  const base = {
    id: sanitizeName(section.id || ''),
    type,
    title: sanitizeString(section.title),
    subtitle: sanitizeString(section.subtitle),
    description: sanitizeString(section.description),
    tint: Boolean(section.tint),
    layout: sanitizeString(section.layout),
    summary: sanitizeString(section.summary),
    cta: normalizeAction(section.cta)
  };

  switch (type) {
    case 'cards':
      return Object.assign(base, {
        cards: toArray(section.cards).map(normalizeCard)
      });
    case 'timeline':
      return Object.assign(base, {
        items: toArray(section.items)
          .map((item) => ({
            year: sanitizeString(item?.year),
            title: sanitizeString(item?.title),
            description: sanitizeString(item?.description)
          }))
          .filter((item) => item.year || item.title || item.description)
      });
    case 'stats':
      return Object.assign(base, {
        stats: toArray(section.stats)
          .map((item) => ({
            value: sanitizeString(item?.value),
            label: sanitizeString(item?.label)
          }))
          .filter((stat) => stat.value || stat.label)
      });
    case 'list':
      return Object.assign(base, {
        items: toArray(section.items)
          .map((item) => ({
            title: sanitizeString(item?.title),
            description: sanitizeString(item?.description)
          }))
          .filter((item) => item.title || item.description)
      });
    case 'contact':
      return Object.assign(base, {
        items: toArray(section.items)
          .map((item) => ({
            label: sanitizeString(item?.label),
            value: sanitizeString(item?.value)
          }))
          .filter((item) => item.label || item.value),
        note: sanitizeString(section.note)
      });
    case 'gallery':
      return Object.assign(base, {
        items: toArray(section.items)
          .map((item) => ({
            src: sanitizeString(item?.src),
            alt: sanitizeString(item?.alt),
            caption: sanitizeString(item?.caption)
          }))
          .filter((item) => item.src)
      });
    case 'richtext':
      return Object.assign(base, {
        html: sanitizeString(section.html)
      });
    default:
      return base;
  }
}

function normalizePageContent(raw = {}) {
  const hero = normalizeHero(raw.hero || {});
  const sections = toArray(raw.sections).map(normalizeSection);
  return {
    hero,
    sections
  };
}

function deriveSummary(content) {
  if (!content) return '';
  const heroSummary = content.hero?.summary || content.hero?.lead || content.hero?.description;
  if (heroSummary) return heroSummary.slice(0, 280);
  const firstSection =
    content.sections?.find((section) => section.description) ||
    content.sections?.find((section) => Array.isArray(section.items) && section.items.length);
  if (firstSection?.description) return firstSection.description.slice(0, 280);
  if (firstSection?.items?.length) {
    const firstItem = firstSection.items.find((item) => item.description) || firstSection.items[0];
    if (firstItem?.description) return firstItem.description.slice(0, 280);
  }
  return '';
}

function normalizePageDocument(raw) {
  const draftSource = raw && typeof raw === 'object' && raw.draft ? raw.draft : null;
  const publishedSource =
    raw && typeof raw === 'object' && raw.published
      ? raw.published
      : raw && typeof raw === 'object' && raw.content
      ? raw.content
      : raw;

  const published = normalizePageContent(publishedSource || {});
  const draft = draftSource ? normalizePageContent(draftSource) : null;

  const metaRaw = raw && typeof raw === 'object' && raw.meta ? raw.meta : {};
  const meta = {
    title: sanitizeString(metaRaw.title),
    summary: sanitizeString(metaRaw.summary) || deriveSummary(published),
    heroImage: sanitizeString(metaRaw.heroImage),
    gallery: toArray(metaRaw.gallery).map((item) => sanitizeString(item)).filter(Boolean),
    tags: toArray(metaRaw.tags).map((tag) => sanitizeString(tag)).filter(Boolean),
    locale: sanitizeString(metaRaw.locale) || 'zh-CN',
    lastPublishedAt: sanitizeString(metaRaw.lastPublishedAt),
    updatedAt: sanitizeString(metaRaw.updatedAt),
    draftUpdatedAt: sanitizeString(metaRaw.draftUpdatedAt),
    cta: normalizeAction(metaRaw.cta)
  };

  const status = raw && typeof raw === 'object' && raw.status === 'draft' ? 'draft' : 'published';

  return {
    status,
    meta,
    published,
    draft
  };
}

function normalizeCubeFace(face = {}) {
  return {
    key: sanitizeName(face.key || ''),
    label: sanitizeString(face.label),
    subtitle: sanitizeString(face.subtitle),
    description: sanitizeString(face.description),
    route: sanitizeRoute(face.route || ''),
    pageRef: sanitizeName(face.pageRef || ''),
    badge: sanitizeString(face.badge),
    image: sanitizeString(face.image),
    colors: (Array.isArray(face.colors) && face.colors.length >= 2
      ? [face.colors[0], face.colors[1]]
      : ['#2B63FF', '#0C246E']
    ).map((color, index) => sanitizeColor(color, index === 0 ? '#2B63FF' : '#0C246E')),
    cta: normalizeAction(face.cta),
    summary: sanitizeString(face.summary)
  };
}

function normalizeCubeDocument(raw) {
  const faces = toArray(raw?.faces).map(normalizeCubeFace);
  return { faces };
}

function normalizeNavigationItem(item = {}) {
  const normalized = {
    key: sanitizeName(item.key || ''),
    label: sanitizeString(item.label),
    route: sanitizeRoute(item.route || ''),
    pageRef: sanitizeName(item.pageRef || ''),
    summary: sanitizeString(item.summary),
    description: sanitizeString(item.description),
    cta: normalizeAction(item.cta),
    visible: item.visible === false ? false : true
  };
  return normalized;
}

function normalizeTheme(raw = {}) {
  const theme = cloneDeep(DEFAULT_THEME);
  const paletteRaw = raw && typeof raw === 'object' ? raw.palette : {};
  const surfacesRaw = raw && typeof raw === 'object' ? raw.surfaces : {};
  const textRaw = raw && typeof raw === 'object' ? raw.text : {};

  theme.palette.primary = sanitizeColor(paletteRaw?.primary, DEFAULT_THEME.palette.primary);
  const accentSource = paletteRaw?.accent ?? paletteRaw?.secondary;
  theme.palette.accent = sanitizeColor(accentSource, DEFAULT_THEME.palette.accent);
  theme.palette.neutral = sanitizeColor(paletteRaw?.neutral, DEFAULT_THEME.palette.neutral);

  theme.surfaces.page = sanitizeColor(surfacesRaw?.page, DEFAULT_THEME.surfaces.page);
  theme.surfaces.nav = sanitizeColor(surfacesRaw?.nav, DEFAULT_THEME.surfaces.nav);
  theme.surfaces.cube = sanitizeColor(surfacesRaw?.cube, DEFAULT_THEME.surfaces.cube);
  const gradientDefaults = DEFAULT_THEME.surfaces.gradients || {};
  const gradientsRaw = surfacesRaw && typeof surfacesRaw.gradients === 'object' ? surfacesRaw.gradients : {};
  const gradients = {};
  Object.keys(gradientDefaults).forEach((key) => {
    gradients[key] = sanitizeGradientConfig(gradientsRaw[key], gradientDefaults[key]);
  });
  theme.surfaces.gradients = gradients;

  theme.text.heading = sanitizeColor(textRaw?.heading, DEFAULT_THEME.text.heading);
  theme.text.body = sanitizeColor(textRaw?.body, DEFAULT_THEME.text.body);
  const inverseSource = textRaw?.inverse ?? textRaw?.button;
  theme.text.inverse = sanitizeColor(inverseSource, DEFAULT_THEME.text.inverse);
  theme.text.interactive = sanitizeColor(textRaw?.interactive, DEFAULT_THEME.text.interactive);

  return theme;
}

function normalizeSiteConfig(raw, { keepAdminToken = false } = {}) {
  const data = raw && typeof raw === 'object' ? raw : {};
  const metaRaw = data.meta && typeof data.meta === 'object' ? data.meta : {};
  const site = {
    companyName: sanitizeString(data.companyName),
    icpRecord: sanitizeString(data.icpRecord),
    homeRoute: sanitizeRoute(data.homeRoute || '/'),
    adminTokenHint: sanitizeString(data.adminTokenHint) || '设置环境变量 ADMIN_TOKEN 或在此文件中修改 adminToken 字段。',
    navigation: toArray(data.navigation).map(normalizeNavigationItem),
    theme: normalizeTheme(data.theme),
    meta: {
      updatedAt: sanitizeString(metaRaw.updatedAt)
    }
  };
  if (keepAdminToken) {
    site.adminToken = sanitizeString(data.adminToken) || getAdminToken();
  }
  return site;
}

function loadSiteConfig() {
  const existing = readJson(SITE_FILE, {});
  const schemaResult = enforceSiteSchema(existing || {});
  if (schemaResult.errors.length) {
    console.warn(`[schema] Site loaded with issues: ${schemaResult.errors.join('; ')}`);
  }
  return normalizeSiteConfig(schemaResult.value || {}, { keepAdminToken: true });
}

function saveSiteConfig(data) {
  writeJson(SITE_FILE, data);
}

function loadCubeDocument() {
  const file = path.join(CONTENT_DIR, 'cube.json');
  const existing = readJson(file, { faces: [] });
  const schemaResult = enforceCubeSchema(existing || {});
  if (schemaResult.errors.length) {
    console.warn(`[schema] Cube loaded with issues: ${schemaResult.errors.join('; ')}`);
  }
  return normalizeCubeDocument(schemaResult.value || {});
}

function saveCubeDocument(data) {
  const file = path.join(CONTENT_DIR, 'cube.json');
  writeJson(file, data);
}

function loadPageDocument(pageId) {
  const file = pageFilePath(pageId);
  const data = readJson(file, null);
  const schemaResult = enforcePageSchema(data || {});
  if (schemaResult.errors.length) {
    console.warn(`[schema] Page ${pageId} loaded with issues: ${schemaResult.errors.join('; ')}`);
  }
  return normalizePageDocument(schemaResult.value || {});
}

function savePageDocument(pageId, doc) {
  const file = pageFilePath(pageId);
  writeJson(file, doc);
}

function cleanupPreviewCache() {
  const now = Date.now();
  for (const [token, entry] of previewCache.entries()) {
    if (!entry || entry.expiresAt <= now) {
      previewCache.delete(token);
    }
  }
}

function storePreviewDocument(pageId, document) {
  cleanupPreviewCache();
  const token = crypto.randomBytes(16).toString('hex');
  previewCache.set(token, {
    pageId,
    document,
    createdAt: Date.now(),
    expiresAt: Date.now() + PREVIEW_TTL
  });
  return { token, expiresAt: Date.now() + PREVIEW_TTL };
}

function pickPreviewDocument(token, pageId) {
  if (!token) return null;
  const entry = previewCache.get(token);
  if (!entry) return null;
  if (entry.expiresAt <= Date.now()) {
    previewCache.delete(token);
    return null;
  }
  if (entry.pageId !== pageId) return null;
  return entry.document;
}

function invalidatePageCache(pageId) {
  if (!pageId) return;
  for (const key of contentCache.keys()) {
    if (key.startsWith(`${pageId}:`)) {
      contentCache.delete(key);
    }
  }
}

function invalidateAllContentCache() {
  contentCache.clear();
}

function buildAggregate(pageId, { mode = 'published', previewDocument = null } = {}) {
  const site = normalizeSiteConfig(loadSiteConfig());
  const cube = loadCubeDocument();
  const document = previewDocument ? normalizePageDocument(previewDocument) : loadPageDocument(pageId);
  const content = mode === 'draft' && document.draft ? document.draft : document.published;
  const summary = document.meta.summary || deriveSummary(document.published);
  const cubeFace =
    cube.faces.find((face) => face.key === pageId) ||
    cube.faces.find((face) => face.pageRef === pageId) ||
    null;
  const navigationItem =
    site.navigation.find((item) => item.key === pageId) ||
    site.navigation.find((item) => item.pageRef === pageId) ||
    null;

  return {
    pageId,
    mode,
    generatedAt: nowIso(),
    meta: Object.assign({}, document.meta, { summary }),
    content,
    site: {
      companyName: site.companyName,
      icpRecord: site.icpRecord,
      homeRoute: site.homeRoute,
      navigation: site.navigation,
      theme: site.theme
    },
    references: {
      cubeFace,
      navigation: navigationItem
    }
  };
}

function resolveAggregate(pageId, { mode = 'published', previewToken = '', refresh = false } = {}) {
  const normalizedMode = mode === 'draft' ? 'draft' : 'published';
  if (previewToken) {
    const preview = pickPreviewDocument(previewToken, pageId);
    if (!preview) return null;
    const aggregate = buildAggregate(pageId, { mode: normalizedMode, previewDocument: preview });
    aggregate.cache = {
      hit: false,
      preview: true,
      ttl: 0
    };
    return aggregate;
  }

  const cacheKey = `${pageId}:${normalizedMode}`;
  const now = Date.now();
  if (!refresh && contentCache.has(cacheKey)) {
    const cached = contentCache.get(cacheKey);
    if (cached && now - cached.timestamp < CACHE_TTL) {
      const payload = cloneDeep(cached.payload);
      payload.cache = {
        hit: true,
        preview: false,
        ttl: Math.max(CACHE_TTL - (now - cached.timestamp), 0)
      };
      return payload;
    }
  }

  const aggregate = buildAggregate(pageId, { mode: normalizedMode });
  contentCache.set(cacheKey, { timestamp: now, payload: aggregate });
  const payload = cloneDeep(aggregate);
  payload.cache = {
    hit: false,
    preview: false,
    ttl: CACHE_TTL
  };
  return payload;
}
function getAdminToken() {
  const envToken = process.env.ADMIN_TOKEN;
  if (envToken && envToken.trim()) return envToken.trim();
  const site = readJson(SITE_FILE, {});
  return site.adminToken || 'changeme-token';
}

function sendJson(res, status, data) {
  res.writeHead(status, {
    'Content-Type': 'application/json; charset=utf-8',
    'Cache-Control': 'no-store'
  });
  res.end(JSON.stringify(data));
}

function sendText(res, status, message) {
  res.writeHead(status, { 'Content-Type': 'text/plain; charset=utf-8' });
  res.end(message);
}

function sanitizeName(name) {
  return name.replace(/[^a-zA-Z0-9_-]/g, '');
}

function isAuthenticated(req) {
  const token = req.headers['x-admin-token'] || req.headers['authorization'];
  if (!token) return false;
  if (token.startsWith('Bearer ')) {
    return token.slice(7) === getAdminToken();
  }
  return token === getAdminToken();
}

function collectBody(req, limit = 10 * 1024 * 1024) {
  return new Promise((resolve, reject) => {
    let size = 0;
    const chunks = [];
    req.on('data', (chunk) => {
      size += chunk.length;
      if (size > limit) {
        reject(new Error('Payload too large'));
        req.destroy();
        return;
      }
      chunks.push(chunk);
    });
    req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
    req.on('error', reject);
  });
}

function serveStatic(filePath, res) {
  fs.stat(filePath, (err, stats) => {
    if (err || !stats.isFile()) {
      sendText(res, 404, 'Not Found');
      return;
    }
    const ext = path.extname(filePath).toLowerCase();
    const type = MIME_TYPES[ext] || 'application/octet-stream';
    res.writeHead(200, { 'Content-Type': type });
    fs.createReadStream(filePath).pipe(res);
  });
}

const server = http.createServer(async (req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);
  const { pathname } = url;

  // API routes
  if (pathname.startsWith('/api/')) {
    if (pathname === '/api/health') {
      return sendJson(res, 200, { status: 'ok' });
    }

    if (pathname === '/api/content/cube' && req.method === 'GET') {
      const data = loadCubeDocument();
      return sendJson(res, 200, data);
    }

    if (pathname === '/api/content/cube' && req.method === 'PUT') {
      if (!isAuthenticated(req)) {
        return sendJson(res, 401, { error: 'Unauthorized' });
      }
      try {
        const raw = await collectBody(req);
        const data = JSON.parse(raw);
        if (!data || typeof data !== 'object') {
          return sendJson(res, 400, { error: 'Invalid payload' });
        }
        const { value, errors } = enforceCubeSchema(data);
        if (errors.length) {
          return sendJson(res, 422, { error: 'Schema validation failed', details: errors });
        }
        const normalized = normalizeCubeDocument(value);
        saveCubeDocument(normalized);
        invalidateAllContentCache();
        return sendJson(res, 200, { success: true });
      } catch (err) {
        return sendJson(res, 400, { error: err.message || 'Invalid JSON' });
      }
    }

    if (pathname === '/api/content/site') {
      if (req.method === 'GET') {
        const data = normalizeSiteConfig(loadSiteConfig());
        return sendJson(res, 200, data || {});
      }
      if (req.method === 'PUT') {
        if (!isAuthenticated(req)) {
          return sendJson(res, 401, { error: 'Unauthorized' });
        }
        try {
          const raw = await collectBody(req);
          const data = JSON.parse(raw);
          if (!data || typeof data !== 'object') {
            return sendJson(res, 400, { error: 'Invalid payload' });
          }
          const { value, errors } = enforceSiteSchema(data);
          if (errors.length) {
            return sendJson(res, 422, { error: 'Schema validation failed', details: errors });
          }
          const normalized = normalizeSiteConfig(value, { keepAdminToken: true });
          const current = loadSiteConfig();
          normalized.adminToken = sanitizeString(data.adminToken) || current.adminToken || getAdminToken();
          normalized.meta = Object.assign({}, current.meta || {}, { updatedAt: nowIso() });
          saveSiteConfig(normalized);
          invalidateAllContentCache();
          return sendJson(res, 200, { success: true });
        } catch (err) {
          return sendJson(res, 400, { error: err.message || 'Invalid JSON' });
        }
      }
    }

    const aggregateMatch = pathname.match(/^\/api\/content\/([^/]+)$/);
    if (aggregateMatch) {
      const rawId = aggregateMatch[1];
      if (['site', 'cube', 'pages', 'uploads'].includes(rawId)) {
        // handled elsewhere
      } else {
        const pageId = sanitizeName(rawId);
        if (!pageId) {
          return sendJson(res, 400, { error: 'Invalid page id' });
        }
        const modeParam = url.searchParams.get('mode');
        const mode = modeParam === 'draft' ? 'draft' : 'published';
        const previewToken = sanitizeString(url.searchParams.get('preview'));
        const refresh = url.searchParams.has('refresh');
        const aggregate = resolveAggregate(pageId, { mode, previewToken, refresh });
        if (!aggregate) {
          return sendJson(res, 404, { error: 'Not Found' });
        }
        return sendJson(res, 200, aggregate);
      }
    }

    const previewMatch = pathname.match(/^\/api\/content\/pages\/([^/]+)\/preview$/);
    if (previewMatch) {
      const pageId = sanitizeName(previewMatch[1] || '');
      if (!pageId) {
        return sendJson(res, 400, { error: 'Invalid page id' });
      }
      if (!isAuthenticated(req)) {
        return sendJson(res, 401, { error: 'Unauthorized' });
      }
      try {
        const raw = await collectBody(req);
        const data = JSON.parse(raw);
        if (!data || typeof data !== 'object') {
          return sendJson(res, 400, { error: 'Invalid payload' });
        }
        const { value, errors } = enforcePageSchema(data);
        if (errors.length) {
          return sendJson(res, 422, { error: 'Schema validation failed', details: errors });
        }
        const normalized = normalizePageDocument(value);
        const now = nowIso();
        normalized.meta.updatedAt = now;
        if (normalized.draft) {
          normalized.meta.draftUpdatedAt = now;
        }
        if (normalized.status === 'published') {
          normalized.meta.lastPublishedAt = now;
        }
        const result = storePreviewDocument(pageId, normalized);
        return sendJson(res, 200, { token: result.token, expiresAt: new Date(result.expiresAt).toISOString() });
      } catch (err) {
        return sendJson(res, 400, { error: err.message || 'Invalid JSON' });
      }
    }

    if (pathname.startsWith('/api/content/pages/')) {
      const pageId = sanitizeName(pathname.split('/').pop() || '');
      if (!pageId) {
        return sendJson(res, 400, { error: 'Invalid page id' });
      }

      if (req.method === 'GET') {
        const document = loadPageDocument(pageId);
        return sendJson(res, 200, document || {});
      }

      if (req.method === 'PUT') {
        if (!isAuthenticated(req)) {
          return sendJson(res, 401, { error: 'Unauthorized' });
        }
        try {
          const raw = await collectBody(req);
          const data = JSON.parse(raw);
          if (!data || typeof data !== 'object') {
            return sendJson(res, 400, { error: 'Invalid payload' });
          }
          const { value, errors } = enforcePageSchema(data);
          if (errors.length) {
            return sendJson(res, 422, { error: 'Schema validation failed', details: errors });
          }
          const normalized = normalizePageDocument(value);
          const now = nowIso();
          normalized.meta.updatedAt = now;
          if (normalized.draft) {
            normalized.meta.draftUpdatedAt = now;
          }
          if (normalized.status === 'published') {
            normalized.meta.lastPublishedAt = now;
            if (!normalized.published && normalized.draft) {
              normalized.published = cloneDeep(normalized.draft);
            }
          }
          savePageDocument(pageId, normalized);
          invalidatePageCache(pageId);
          return sendJson(res, 200, { success: true });
        } catch (err) {
          return sendJson(res, 400, { error: err.message || 'Invalid JSON' });
        }
      }
    }

    if (pathname === '/api/uploads' && req.method === 'POST') {
      if (!isAuthenticated(req)) {
        return sendJson(res, 401, { error: 'Unauthorized' });
      }
      try {
        const raw = await collectBody(req, 20 * 1024 * 1024);
        const payload = JSON.parse(raw);
        if (!payload || typeof payload !== 'object' || !payload.data) {
          return sendJson(res, 400, { error: 'Invalid payload' });
        }
        const buffer = Buffer.from(payload.data, 'base64');
        if (!buffer.length) {
          return sendJson(res, 400, { error: 'Invalid image data' });
        }
        const safeName = sanitizeName(payload.filename || `cube-face-${Date.now()}`);
        const extRaw = (payload.extension || 'png').toString().toLowerCase();
        const sanitizedExt = extRaw.replace(/[^a-z0-9]/g, '');
        const ext = sanitizedExt ? `.${sanitizedExt}` : '.png';
        const fileName = `${safeName || `cube-face-${Date.now()}`}${ext}`;
        const filePath = path.join(UPLOAD_DIR, fileName);
        fs.writeFileSync(filePath, buffer);
        return sendJson(res, 200, { success: true, path: `uploads/${fileName}` });
      } catch (err) {
        return sendJson(res, 400, { error: err.message || 'Failed to upload' });
      }
    }

    return sendJson(res, 404, { error: 'Not Found' });
  }

  // Static assets & fallback
  let filePath = path.join(PUBLIC_DIR, pathname === '/' ? 'index.html' : pathname);
  if (!filePath.startsWith(PUBLIC_DIR)) {
    return sendText(res, 403, 'Forbidden');
  }
  fs.stat(filePath, (err, stats) => {
    if (!err && stats.isDirectory()) {
      const indexFile = path.join(filePath, 'index.html');
      return serveStatic(indexFile, res);
    }
    if (err) {
      // fallback to index.html for SPA-like navigation
      return serveStatic(path.join(PUBLIC_DIR, 'index.html'), res);
    }
    return serveStatic(filePath, res);
  });
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`);
});
function pageFilePath(pageId) {
  return path.join(PAGES_DIR, `${sanitizeName(pageId)}.json`);
}
